package com.mytijian.openapi.adapter.dianping.service.impl;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

import com.mytijian.openapi.adapter.dianping.param.DzCancelbookParam;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.mytijian.openapi.adapter.dianping.client.DzopenUtils;
import com.mytijian.openapi.adapter.dianping.consts.AsyncRequestConsts;
import com.mytijian.openapi.adapter.dianping.consts.DzopenConsts;
import com.mytijian.openapi.adapter.dianping.exceptions.DzopenErrorInfo;
import com.mytijian.openapi.adapter.dianping.exceptions.DzopenException;
import com.mytijian.openapi.adapter.dianping.inner.param.DzAsyncOrderConfirmParam;
import com.mytijian.openapi.adapter.dianping.model.AsyncRequest;
import com.mytijian.openapi.adapter.dianping.model.DzopenOrderRel;
import com.mytijian.openapi.adapter.dianping.param.AsyncRequestQuery;
import com.mytijian.openapi.adapter.dianping.param.DzStartBookParam;
import com.mytijian.openapi.adapter.dianping.param.DzopenOrderRelQuery;
import com.mytijian.openapi.adapter.dianping.result.DzResult;
import com.mytijian.openapi.adapter.dianping.result.DzStartbookResult;
import com.mytijian.openapi.adapter.dianping.service.AsyncRequestService;
import com.mytijian.openapi.adapter.dianping.service.DzopenOrderRelService;
import com.mytijian.openapi.adapter.dianping.service.DzopenStartbookService;
import com.mytijian.openapi.core.client.IOpenAPIClient;
import com.mytijian.openapi.core.client.param.BookParam;
import com.mytijian.openapi.core.client.param.RegisterParam;
import com.mytijian.openapi.core.client.result.BookResult;
import com.mytijian.openapi.core.client.result.MealInfo;
import com.mytijian.openapi.core.client.result.RegisterResult;
import com.mytijian.openapi.core.model.AppConfig;
import com.mytijian.openapi.core.service.OpenApiAppConfigService;
import com.mytijian.openapi.core.utils.MD5;
import com.mytijian.utils.DateUtils;
import com.mytijian.utils.Lists;


@Service("dzopenStartbookService")
public class DzopenStartbookServiceImpl implements DzopenStartbookService {

	private Logger logger = LoggerFactory.getLogger(getClass());

	@Autowired
	private AsyncRequestService asyncRequestService;

	@Autowired
	private DzopenOrderRelService dzopenOrderRelService;

	@Autowired
	private OpenApiAppConfigService openApiAppConfigService;

	@Autowired
	private DzopenShopAuthServiceImpl dzopenShopAuthServiceImpl;

	@Autowired
	private DzopenCancelbookServiceImpl dzopenCancelbookServiceImpl;

	@Autowired
	private IOpenAPIClient client;
	
	@Value("${dzopen.signkey}")
	private String signkey;
	
	@Value("${dzopen.appkey}")
	private String appkey;
	
	@Value("${dzopen.asyncmaxtrycount}")
	private Integer maxTryCount;
	
	@Override
	public DzStartbookResult startbook(DzStartBookParam param) {

		String orderId = param.getOrderId();
		
		ensureDzOrderIdNotDuplicate(orderId);

		initDzOrderRel(orderId, param.getAppShopId());

		saveAsyncDzOrderBookReqeust(param);

		DzStartbookResult bookResult = new DzStartbookResult();
		bookResult.setOrder_id(param.getOrderId());
		return bookResult;
	}

	/**
	 * 如果大众点评订单ID重复则抛出异常
	 * @param orderId
	 */
	private void ensureDzOrderIdNotDuplicate(String orderId) {
		DzopenOrderRelQuery relQuery = new DzopenOrderRelQuery();
		relQuery.setDzOrderId(orderId);

		DzopenOrderRel rel = dzopenOrderRelService.getOrderRel(relQuery);

		if (rel != null) {
			throw new DzopenException(DzopenErrorInfo.ORDER_BOOK_DUPLICATE_ORDER_ID);
		}
	}

	/**
	 * 在大众点评订单关系表中 建立基础信息 (仅大众点评订单ID)
	 * @param orderId
	 */
	private void initDzOrderRel(String orderId, String appShopId) {
		DzopenOrderRel dzOrderRel = new DzopenOrderRel();
		dzOrderRel.setDzOrderId(orderId);
		dzOrderRel.setHospitalId(Integer.valueOf( appShopId));
		dzopenOrderRelService.addDzopenOrderRel(dzOrderRel);
	}
	
	/**
	 * 保存下单请求信息
	 * @param param
	 */
	private void saveAsyncDzOrderBookReqeust(DzStartBookParam param) {
		String requestParams = JSON.toJSONString(param);
		AsyncRequest async = new AsyncRequest();
		async.setRequestApp(AsyncRequestConsts.Apps.Dzopen);
		async.setRequestStatus(AsyncRequestConsts.RequestStatus.Unexecuted);
		async.setRequestType(AsyncRequestConsts.DzopenApp.startbook);
		async.setRequestParam(requestParams);
		async.setRequestTryCount(0);
		async.setAsyncNotifyStatus(AsyncRequestConsts.NotifyStatus.Unsuccess);
		async.setAsyncNotifyTryCount(0);
		asyncRequestService.saveRequest(async);
	}

	

	@Override
	public void asyncBook() {
		List<AsyncRequest> requests = getNeedBookAsyncRequest();
		if (Lists.isEmpty(requests)) {
			logger.info("无异步下单请求");
			return;
		}
		logger.info("需要处理 {} 条异步下单请求。", requests.size());
		requests.forEach(this::doBook);
	}

	

	@Override
	public void asyncNotify() {
		
		List<AsyncRequest> requests = getNeedNotifyAsyncRequests();

		if (Lists.isEmpty(requests)) {
			logger.info("无异步下单通知请求");
			return;
		}
		logger.info("需要处理 {} 条异步下单通知。", requests.size());

		for (AsyncRequest request : requests) {
			request.setAsyncNotifyTryCount(request.getAsyncNotifyTryCount() + 1);
			this.asyncRequestService.updateRequest(request);
			DzStartBookParam dzParam = JSON.parseObject(request.getRequestParam(), DzStartBookParam.class);
			logger.info("美团订单{} 开始重发通知 尝试第{}次 ", dzParam.getOrderId(), request.getAsyncNotifyTryCount());

			DzopenOrderRelQuery orderRelQuery = new DzopenOrderRelQuery();
			orderRelQuery.setDzOrderId(dzParam.getOrderId());
			DzopenOrderRel rel = dzopenOrderRelService.getOrderRel(orderRelQuery);
			
			DzResult<?> asyncNotifyResult = this.asyncNotify(dzParam, rel.getOrderNum(), request.getRequestResult());
			
			logger.info("美团订单{}，关联订单{} ", dzParam.getOrderId(), rel.getOrderNum());

			if (DzopenConsts.SuccessCode.equals(asyncNotifyResult.getCode())) {
				request.setAsyncNotifyStatus(AsyncRequestConsts.NotifyStatus.Successed);
				logger.info("美团订单{}，关联订单{} 异步下单通知成功 美团接口返回{}", dzParam.getOrderId(), rel.getOrderNum(), ToStringBuilder.reflectionToString(asyncNotifyResult));
			} else {
				logger.info("美团订单{}，关联订单{} 异步下单通知失败 美团接口返回{}", dzParam.getOrderId(), rel.getOrderNum(), ToStringBuilder.reflectionToString(asyncNotifyResult));
				DzCancelbookParam dzCancelbookParam = new DzCancelbookParam();
				BeanUtils.copyProperties(dzParam,dzCancelbookParam);
				dzCancelbookParam.setReason("内部取消");
				dzopenCancelbookServiceImpl.cancelbook(dzCancelbookParam);
			}

			this.asyncRequestService.updateRequest(request);
		}
	}

	

	private void doBook(AsyncRequest request) {
		
		try {
		request.setRequestTryCount(request.getRequestTryCount() + 1);
		asyncRequestService.updateRequest(request);
		
		// 如果任务开始前查一次后续每次都使用同一个config有可能碰到 accessToken 被update问题
		// 订单数据量不大的时候每个单查一次数据库是最简单的办法
		// 有一定数量的时候在优化无妨。
		AppConfig config = openApiAppConfigService.getConfig(DzopenConsts.AppName);

		DzStartBookParam dzParam = JSON.parseObject(request.getRequestParam(), DzStartBookParam.class);

		RegisterResult userResult = registerOrUpdateUser(config, dzParam);

		if (userResult == null || StringUtils.isEmpty(userResult.getOpenId())) {
			logger.warn("[美团点评] [下单]  注册/更新用户信息失败，开发平台返回: {}", JSON.toJSON(userResult));
			asyncRequestService.updateRequest(request);
			return;
		}
		logger.info("[美团点评] [下单] 注册/更新用户信息成功，开发平台返回：{}",JSON.toJSON(userResult));
		BookResult openAPIBookResult = bookOrder(config, dzParam, userResult);
		request.setRequestStatus(AsyncRequestConsts.RequestStatus.Executed);
		
		if (verifyBookResult(openAPIBookResult) == false) {
			logger.error("[美团点评] [下单] 执行异步下单请求失败。原因：开放平台下单接口调用失败，返回值：{}", ToStringBuilder.reflectionToString(openAPIBookResult));
			request.setRequestResult(String.valueOf(DzopenConsts.BookStatus.Failed));
			asyncRequestService.updateRequest(request);
		} else {
			request.setRequestResult(String.valueOf(DzopenConsts.BookStatus.Successed));
			updateDzOrderRel(dzParam, openAPIBookResult);
			asyncRequestService.updateRequest(request);
		}

		// 调用大众点评接口 获取返回值
		DzResult<?> asyncNotifyResult = asyncNotify(dzParam, openAPIBookResult.getOrderNum(), request.getRequestResult());
		request.setAsyncNotifyTryCount(request.getAsyncNotifyTryCount() + 1);
		
		if (DzopenConsts.SuccessCode.equals(asyncNotifyResult.getCode())) {
			request.setAsyncNotifyStatus(AsyncRequestConsts.NotifyStatus.Successed);
			logger.info("[美团点评] [异步下单] 回调通知美团成功");
		}
		
		// 更新异步调用记录
		asyncRequestService.updateRequest(request);
		} catch (Exception e) {
			logger.error("【美团点评】[异步下单] 下单失败",e);
			e.printStackTrace();
		}
	}

	
	private RegisterResult registerOrUpdateUser(AppConfig config, DzStartBookParam dzParam) {
		RegisterParam userInfo = new RegisterParam();
		userInfo.setUsername(dzParam.getUserName());
		userInfo.setIdcard(dzParam.getIdentityNum());
		userInfo.setMobile(dzParam.getMobile());
		logger.info("【美团点评】 [open-api]注册接口 用户名={},身份证={},电话={} ",userInfo.getUsername(),userInfo.getIdcard(),userInfo.getMobile());
		RegisterResult userResult = client.register(config, userInfo);
		return userResult;
	}

	private void updateDzOrderRel(DzStartBookParam dzParam, BookResult br) {
		DzopenOrderRelQuery query = new DzopenOrderRelQuery();
		query.setDzOrderId(dzParam.getOrderId());
		DzopenOrderRel rel = dzopenOrderRelService.getOrderRel(query);
		if (rel != null) {
			rel.setOrderNum(br.getOrderNum());
			rel.setExamDate(DateUtils.parse(br.getExamDate()));
			rel.setOrderConsumeStatus(DzopenConsts.OrderConsumeStatus.normal);
			dzopenOrderRelService.updateDzopenOrderRel(rel);
		} else {
			rel = new DzopenOrderRel();
			rel.setDzOrderId(dzParam.getOrderId());
			rel.setOrderConsumeStatus(1);
			rel.setExamDate(DateUtils.parse(br.getExamDate()));
			rel.setOrderNum(br.getOrderNum());
			rel.setOrderConsumeStatus(DzopenConsts.OrderConsumeStatus.normal);
			dzopenOrderRelService.addDzopenOrderRel(rel);
		}
	}

	private DzResult<?> asyncNotify(DzStartBookParam dzParam, String orderNum, String bookStatus) {
		
		DzAsyncOrderConfirmParam confirmParam = new DzAsyncOrderConfirmParam();
		confirmParam.setAppKey(dzParam.getAppKey());
		confirmParam.setTimestamp(DateUtils.format(DateUtils.YYYY_MM_DD_HMS, new Date()));
		confirmParam.setV("1");
		confirmParam.setCode(String.valueOf(DzopenConsts.SuccessCode));
		confirmParam.setSignMethod("MD5");
		confirmParam.setFormat("json");

		confirmParam.setAppOrderId(orderNum);
		confirmParam.setOrderId(dzParam.getOrderId());
		confirmParam.setAppShopId(dzParam.getAppShopId());
		confirmParam.setBookStatus(bookStatus);
		confirmParam.setSession(dzopenShopAuthServiceImpl.getSession(dzParam.getAppShopId()));
		
		DzResult<?> dzResult = DzopenUtils.sendRequest(DzopenConsts.Apis.Bookresultcallback, 
					signkey, confirmParam);

		return dzResult;
	}

	private BookResult bookOrder(AppConfig config, DzStartBookParam dzParam, RegisterResult userResult) {
		
		MealInfo meal = client.getMealByTmallProductId(config, dzParam.getAppProductId());
		
		if (meal==null || meal.getErrCode()!=null) {
			logger.error("[美团点评][异步下单] 查询渠道套餐映射信息为空。下单参数：{}", ToStringBuilder.reflectionToString(dzParam));
			throw new DzopenException(DzopenErrorInfo.ORDER_BOOK_UNKNOW);
		}
		logger.info("[美团点评][异步下单] 查询渠道套餐映射信息。返回结果：{}",ToStringBuilder.reflectionToString(meal));
		BookParam param = new BookParam();

		param.setExamAccountName(MD5.stringMD5(dzParam.getUserName() + config.getAppSignKey()));
		param.setIdCard(MD5.stringMD5(dzParam.getIdentityNum() + config.getAppSignKey()));
		param.setMobile(MD5.stringMD5(dzParam.getMobile() + config.getAppSignKey()));

		Integer dzOrderPrice = new BigDecimal(dzParam.getAmount()).intValue();
		param.setOrderPrice(meal.getPrice());
		param.setExamTimeIntervalId(Integer.parseInt(dzParam.getAppPeriodId()));
		param.setMealId(meal.getMealId());
		param.setExamDate(dzParam.getBegintime().substring(0, 11));
		param.setAgencySellingPrice(dzOrderPrice);
		param.setOpenId(userResult.getOpenId());
		param.setAgencyGoodsId(String.valueOf(meal.getMealId()));
		
		BookResult result = client.book(config, param);
		return result;
	}

	private boolean verifyBookResult(BookResult b) {
		if (b == null) {
			logger.error("[美团点评] [下单] 下单失败 开放平台未返回数据");
			return false;
		}
		if (StringUtils.isNotEmpty(b.getErrCode())) {
			logger.error("[美团点评] [下单] 下单失败 openapi返回: {}", JSON.toJSONString(b));
			return false;
		}

		if (StringUtils.isEmpty(b.getOrderNum())) {
			logger.error("[美团点评] [下单] 下单失败 openapi返回: {}", JSON.toJSONString(b));
			return false;
		}

		return true;
	}
	
	private List<AsyncRequest> getNeedBookAsyncRequest() {
		AsyncRequestQuery query = new AsyncRequestQuery();
		query.setRequestApp(AsyncRequestConsts.Apps.Dzopen);
		query.setRequestStatus(AsyncRequestConsts.RequestStatus.Unexecuted);
		query.setRequestType(AsyncRequestConsts.DzopenApp.startbook);
		query.setRequestTryCountLessThen(maxTryCount);
		List<AsyncRequest> requests = asyncRequestService.listRequests(query);
		return requests;
	}
	
	private List<AsyncRequest> getNeedNotifyAsyncRequests() {
		AsyncRequestQuery asyncRequestQuery = new AsyncRequestQuery();
		asyncRequestQuery.setRequestApp(AsyncRequestConsts.Apps.Dzopen);
		asyncRequestQuery.setRequestStatus(AsyncRequestConsts.RequestStatus.Executed);
		asyncRequestQuery.setRequestType(AsyncRequestConsts.DzopenApp.startbook);
		asyncRequestQuery.setAsyncNotifyStatus(AsyncRequestConsts.NotifyStatus.Unsuccess);
		asyncRequestQuery.setAsyncNotifyTryCountLessThen(maxTryCount);

		List<AsyncRequest> requests = asyncRequestService.listRequests(asyncRequestQuery);
		return requests;
	}

}
